////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.chartClasses
{

import flash.events.Event;
import mx.charts.AxisLabel;
import mx.core.mx_internal;

use namespace mx_internal;

/**
 *  The NumericAxis class acts as a common base class
 *  for axis types representing a continuous range of values
 *  between a defined minimum and maximum.
 *  The built-in LinearAxis, LogAxis, and DateTimeAxis
 *  classes all extend this base class.
 *  
 *  @see mx.charts.DateTimeAxis
 *  @see mx.charts.LinearAxis
 *  @see mx.charts.LogAxis
 */
public class NumericAxis extends AxisBase implements IAxis
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function NumericAxis()
	{
		super();
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	private var _labelSet:AxisLabelSet;
	
	/**
	 *  @private 
	 */
	private var _cachedDataDescriptions:Array;

	/**
	 *  @private
	 */
	private var _cachedValuesHaveBounds:Boolean;
	
	/**
	 *  @private
	 */
	private var _regenerateAutoValues:Boolean = true;

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------
	
    //----------------------------------
	//  assignedMaximum
    //----------------------------------

	/**
	 *  The explicitly assigned maximum value.
	 *  If no value has been assigned, this will be <code>NaN</code>.
	 *  Typically, calculations should be performed
	 *  with the <code>computedMaximum</code> field.
	 */
	protected var assignedMaximum:Number;

    //----------------------------------
	//  assignedMinimum
    //----------------------------------

	/**
	 *  The explicitly assigned minimum value.
	 *  If no value has been assigned, this will be <code>NaN</code>.
	 *  Typically calculations should be performed
	 *  with the <code>computedMinimum</code> field.
	 */
	protected var assignedMinimum:Number;
	
    //----------------------------------
	//  autoAdjust
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the autoAdjust property.
	 */
	private var _autoAdjust:Boolean = true;

    [Inspectable(category="General", defaultValue="true")]

	/**
	 *  Specifies whether Flex rounds values.
	 *  If <code>false</code>, Flex does not round the values
	 *  set by the <code>minimum</code> and <code>maximum</code> properties,
	 *  or modify the default <code>minimum</code> and
	 *  <code>maximum</code> values.
	 *  
	 *  @default true
	 */
	public function get autoAdjust():Boolean 
	{
		return _autoAdjust;
	}
	
	/**
	 *  @private
	 */
	public function set autoAdjust(value:Boolean):void
	{
		_autoAdjust = value;

		dataChanged();
	}

    //----------------------------------
	//  baseAtZero
    //----------------------------------

    [Inspectable(category="General", defaultValue="true")]

	/**
	 *  Specifies whether Flex tries to keep the <code>minimum</code>
	 *  and <code>maximum</code> values rooted at zero.
	 *  If all axis values are positive, the minimum axis value is zero.
	 *  If all axis values are negative, the maximum axis value is zero.  
	 *  
	 *  @default true
	 */
	public var baseAtZero:Boolean = true;

    //----------------------------------
	//  computedInterval
    //----------------------------------

	/**
	 *  The computed interval represented by this axis.
	 *  The <code>computedInterval</code> is used
	 *  by the AxisRenderer and Gridlines classes
	 *  to determine where to render tick marks and grid lines.
	 *	The NumericAxis base class watches this field for changes
	 *  to determine if the chart needs to be re-rendered.
	 *  Derived classes are responsible for computing the value
	 *  of this field.
	 */
	protected var computedInterval:Number;
	
    //----------------------------------
	//  computedMaximum
    //----------------------------------

	/**
	 *  The computed maximum value represented by this axis.
	 *  If the user has explicitly assigned a maximum value,
	 *  the <code>computedMaximum</code> and
	 *  <code>assignedMaximum</code> properties
	 *  are usually the same.
	 *  Otherwise, the <code>computedMaximum</code> is generated
	 *  from the values represented in the chart.
	 */
	protected var computedMaximum:Number
	
    //----------------------------------
	//  computedMinimum
    //----------------------------------

	/**
	 *  The computed minimum value represented by this axis.
	 *  If the user has explicitly assigned a minimum value,
	 *  the <code>computedMinimum</code> and
	 *  <code>assignedMinimum</code> properties
	 *  are usually be the same.
	 *  Otherwise, the <code>computedMinimum</code> is generated
	 *  from the values represented in the chart.
	 */
	protected var computedMinimum:Number;

    //----------------------------------
	//  labelCache
    //----------------------------------

	/**
	 *  The most recent set of AxisLabel objects
	 *  generated to represent this axis.
	 *  This property is <code>null</code> if the axis
	 *  has been modified and requires new labels.
	 *  To guarantee that the value of the <code>labelCache</code> property
	 *  is correct, call the <code>buildLabelCache()</code> method
	 *	before accessing the <code>labelCache</code> property.
	 */
	protected var labelCache:Array;
	
    //----------------------------------
	//  labelFunction
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the labelFunction property.
	 */
	private var _labelFunction:Function = null;

    [Inspectable(category="General")]
	
	/**
	 *  Called to format axis values for display as labels.
	 *  A <code>labelFunction</code> has the following signature:<br></br>
	 *  <pre>
	 *  function labelFunction(<i>labelValue</i>:Object, <i>previousValue</i>:Object, <i>axis</i>:IAxis):String
	 *  </pre>
	 *  
	 *  <p>If you know the types of the data your function will be formatting,
	 *  you can specify an explicit type for the <code>labelValue</code>
	 *  and <code>previousValue</code> parameters.</p>
	 */
	public function get labelFunction():Function
	{
		return _labelFunction;
	}
	
	/**
	 *  @private
	 */
	public function set labelFunction(value:Function):void
	{
		_labelFunction = value;

		invalidateCache();

		dispatchEvent(new Event("mappingChange"));
		dispatchEvent(new Event("axisChange"));
	}

    //----------------------------------
	//  labelMaximum
    //----------------------------------

	/**
	 *  The maximum value where a label should be placed.
	 *  After computing an adjusted minimum value,
	 *  many axis types expand the range of the axis further
	 *  to make room for additional rendering artifacts in the chart,
	 *  such as labels and borders.
	 *  This value represents the maximum value in the chart
	 *  <em>before</em> it is adjusted for these artifacts.
	 *  Typically axes generate labels to make sure
	 *  this value is labeled, rather than the adjusted maximum of the axis.
	 */
	protected var labelMaximum:Number;

    //----------------------------------
	//  labelMinimum
    //----------------------------------

	/**
	 *  The minimum value where a label should be placed.
	 *  After computing an adjusted minimum value,
	 *  many axis types expand the range of the axis further
	 *  to make room for additional rendering artifacts in the chart,
	 *  such as labels and borders.
	 *  This value represents the minimum value in the chart
	 *  <em>before</em> it is adjusted for these artifacts.
	 *  Typically axes will generate labels to make sure
	 *  this value is labeled, rather than the adjusted minimum of the axis.
	 */
	protected var labelMinimum:Number;
	
    //----------------------------------
	//  mappedMaximum
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The computed minimum value for the axis
	 *  as long as this value is greater than 0.
	 *  If the maximum value is less than or equal to 0,
	 *  then the <code>baseline</code> property is the computed maximum.
	 *  If neither value is greater than 0,
	 *  then the <code>baseline</code> property is 0.
	 */
	public function get baseline():Number
	{
		var baseVal:Number;
		
		if (computedMinimum >= 0)
			baseVal = computedMinimum;				
		else if (computedMaximum <= 0)
			baseVal = computedMaximum;
		else
			baseVal = 0;

		return baseVal;
	}

    //----------------------------------
	//  minorTickCache
    //----------------------------------

	/**
	 *  The most recent set of minor tick marks generated to represent this axis.
	 *  This property may be <code>null</code> if the axis
	 *  has been modified and requires new labels and tick marks.
	 *  Use the public accessor <code>minorTicks</code>
	 *  to build the minor tick marks on demand.
	 */
	protected var minorTickCache:Array;
	
    //----------------------------------
	//  minorTicks
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  An Array of minor tick marks generated to represent this axis.
	 */
	public function get minorTicks():Array
	{
		if (!minorTickCache)
			minorTickCache = buildMinorTickCache();
		
		return minorTickCache;
	}

    //----------------------------------
	//  padding
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the padding property.
	 */
	private var _padding:Number;

    [Inspectable(category="General")]

	/**
	 *  Specifies padding that Flex adds to the calculated minimum and maximum
	 *  values for the axis when rendering the values on the screen.  
	 */
	public function get padding():Number
	{
		return _padding;
	}

	/**
	 *  @private
	 */
	public function set padding(value:Number):void
	{
		_padding = value;

		invalidateCache();

		dispatchEvent(new Event("axisChange"));
	}
	
    //----------------------------------
	//  parseFunction
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the parseFunction property.
	 */
	private var _parseFunction:Function = null;  

	[Inspectable(category="Other")]
	
	/** 
	 *  Specify a <code>parseFunction</code> to customize how
	 *  the values rendered by your chart are converted into numeric values.
	 *  A custom <code>parseFunction</code> is passed a data value
	 *  and should return a corresponding number representing the same value.
	 *  By default, this axis uses the ECMA function <code>parseFloat()</code>.
	 */
	public function get parseFunction():Function
	{
		return _parseFunction;
	}
	
	/**
	 *  @private
	 */
	public function set parseFunction(value:Function):void
	{
		_parseFunction = value;

		invalidateCache();
		_cachedDataDescriptions = null;

		dispatchEvent(new Event("mappingChange"));
		dispatchEvent(new Event("axisChange"));		
	}

    //----------------------------------
	//  requiredDescribedFields
    //----------------------------------

	/**
	 *  The fields of the DescribeData structure that this axis is interested in.
	 */
	protected function get requiredDescribedFields():uint
	{
		return DataDescription.REQUIRED_MIN_MAX |
			   DataDescription.REQUIRED_BOUNDED_VALUES;
	}

    //----------------------------------
	//  ticks
    //----------------------------------

	/**
	 *  An Array of tick marks for this axis.
	 */
	protected function get ticks():Array
	{
		var result:Array = [];

		var n:int = labelCache.length;
		for (var i:int = 0; i < n; i++)
		{
			result.push(labelCache[i].position);	
		}		
		
		return result;
	}

    //----------------------------------
	//  zeroValue
    //----------------------------------

	/**
	 *  @private
	 */
	mx_internal function get zeroValue():Number
	{
		return 0;
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: AxisBase
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override public function dataChanged():void
	{
		// We don't call invalidateCache() here, because invalidateCache()
		// forces a rebuild of the labels.
		// But just changing the data doesn't guarantee the labels have changed.
		// Instead, we need to recalc our autogenerated values,
		// and then see if the values have changed.
		minorTickCache = null;
		_cachedDataDescriptions = null;
		_regenerateAutoValues = true;			

		if (isNaN(assignedMinimum) || isNaN(assignedMaximum))
		{
			dispatchEvent(new Event("mappingChange"));
			dispatchEvent(new Event("axisChange"));
		}
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @copy mx.charts.chartClasses.IAxis#mapCache()
	 */
	public function mapCache(cache:Array, field:String,
							 convertedField:String,
							 indexValues:Boolean = false):void
	{
		var n:int = cache.length;
		var i:int;
		
		var v:Object;
		
		if (_parseFunction != null)
		{
			for (i = 0; i < n; i++)
			{
				v = cache[i];				
				v[convertedField] = _parseFunction(v[field]);
			}		
		}
		else
		{
			for (i = 0; i < n && cache[i][field] == null; i++)
			{
			}
			if (i == n)
				return;
				
			if ((cache[i][field] is String))
			{
				for (; i < n; i++)
				{
					v = cache[i];
					v[convertedField] = Number(v[field]);
				}
			}
			else if (cache[i][field] is XML ||
					 cache[i][field] is XMLList)
			{
				for (; i < n; i++)
				{
					v = cache[i];
					v[convertedField] = Number(v[field].toString());
				}				
			}
			else if (cache[i][field] is Number ||
					 cache[i][field] is int ||
					 cache[i][field] is uint)
			{
				for (; i < n; i++)
				{
					v = cache[i];
					v[convertedField] = v[field] == null ? NaN : v[field];
				}
			}
			else
			{
				for (; i < n; i++)
				{
					v = cache[i];
					v[convertedField] = parseFloat(v[field]);
				}
			}
		}
	}

	/**
	 *  @copy mx.charts.chartClasses.IAxis#filterCache()
	 */
	public function filterCache(cache:Array, field:String,
							    filteredField:String):void
	{
		update();

		// Avoid roundoff errors.
		var max:Number = computedMaximum + 0.00001;
		var min:Number = computedMinimum - 0.00001;
		
		var n:int = cache.length;
		for (var i:int = 0; i < n; i++)
		{
			var v:Object = cache[i][field];
			cache[i][filteredField] = v >= min && v <= max ? v : NaN;
		}
	}

	/**
	 *  @copy mx.charts.chartClasses.IAxis#transformCache()
	 */
	public function transformCache(cache:Array, field:String,
								   convertedField:String):void
	{
		update();

		var r:Number = computedMaximum - computedMinimum; 

		var n:int = cache.length;
		for (var i:int = 0; i < n; i++)
		{
			cache[i][convertedField] = (cache[i][field] - computedMinimum) / r;
		}
	}

	/**
	 *  @copy mx.charts.chartClasses.IAxis#invertTransform()
	 */
	public function invertTransform(value:Number):Object
	{
		update();
		return value * (computedMaximum - computedMinimum) + computedMinimum;
	}
	
	/**
	 *  @copy mx.charts.chartClasses.IAxis#formatForScreen()
	 */
	public function formatForScreen(value:Object):String	
	{
		return value.toString();
	}

	/**
	 *  @copy mx.charts.chartClasses.IAxis#getLabelEstimate()
	 */
	public function getLabelEstimate():AxisLabelSet
	{
		update();
		
		var updated:Boolean = buildLabelCache();
		if (updated)
		{			
			_labelSet = new AxisLabelSet();
			_labelSet.labels = labelCache;
			_labelSet.accurate = _cachedValuesHaveBounds == false;
			_labelSet.minorTicks = minorTicks;
			_labelSet.ticks = ticks;
		} 

		return _labelSet;
	}

	/**
	 *  @copy mx.charts.chartClasses.IAxis#preferDropLabels()
	 */
	public function preferDropLabels():Boolean
	{
		return true;
	}

	/**
	 *  @copy mx.charts.chartClasses.IAxis#getLabels()
	 */
	public function getLabels(minimumAxisLength:Number):AxisLabelSet
	{
		var updated:Boolean;
		if (_cachedValuesHaveBounds || labelCache == null)
		{
			_regenerateAutoValues = true;		
			updateCache(true,minimumAxisLength);
			updated = buildLabelCache();
		}
		else
		{
			updated = false;
		}
	
		if (updated)
		{
			_labelSet = new AxisLabelSet();
			_labelSet.labels = 	labelCache;
			_labelSet.minorTicks = minorTicks;
			_labelSet.ticks = ticks;
		}

		return _labelSet;
	}

	/**
	 *  @copy mx.charts.chartClasses.IAxis#reduceLabels()
	 */
	public function reduceLabels(intervalStart:AxisLabel,
								 intervalEnd:AxisLabel):AxisLabelSet
	{
		return _labelSet;
	}

	/**
	 *  Populates the <code>labelCache</code> property with labels representing the current 
	 *  values of the axis. Subclasses must implement this function. This function is called 
	 *  many times, so you should check to see if the <code>labelCache</code> property 
	 *  is <code>null</code> before performing any calculations.
	 *  
	 *  @return true if the labels were regenerated.
	 */
	protected function buildLabelCache():Boolean
	{
		return false;
	}

	/**
	 *  Builds an Array of positions for the minor tick marks Array that is generated by this axis.  
	 *  Subclasses must implement this function. This function is  called automatically 
	 *  by the NumericAxis. You should access the <code>minorTicks</code> property 
	 *  instead of calling this function directly.
	 *  
	 *  @return An Array of positions from 0 to 1 that represent points between the axis 
	 *  minimum and maximum values where minor tick marks are rendered.
	 */
	protected function buildMinorTickCache():Array
	{
		return [];
	}

	/**
	* @private
	 */
	private function updateCache(checkForMargins:Boolean,
								   minimumAxisLength:Number):void
	{
		if (_regenerateAutoValues)
		{
			// We're going to remember these.
			// As an optimization, if after going through all of this
			// the values that generate our labels haven't changed,
			// we won't bother regenerating the labels.
			var oldMin:Number = computedMinimum;
			var oldMax:Number = computedMaximum;
			var oldInterval:Number = computedInterval;
			
			// First we check and see if the author has provided
			// any set min/max values.
			if (!isNaN(assignedMinimum))
				computedMinimum = assignedMinimum;
			if (!isNaN(assignedMaximum))
				computedMaximum = assignedMaximum;
				
			// If either min or max is unset, we need to generate it.
			var autoGen:Boolean = isNaN(assignedMinimum) ||
								  isNaN(assignedMaximum);
			
			// If we still need either a min or a max, we'll autogenerate here.
			if (autoGen)
				autoGenerate(checkForMargins, minimumAxisLength);

			// At this point we know we've got a valid min/max pair,
			// either specified by the author or generated from the data.
			// Next, we go into our adjustment routine.
			// This is where we push those min/max values out to be nice,
			// clean values that look good when you make labels out of them.
			// The same algorithm is also how we generate a nice usable interval
			// from the min/max values we have.so we're going to execute
			// this section if either the user has asked us to autoadjust
			// the min/max to nice clean values, or if the interval hasn't
			// been set by the user.
			// Once we've invoked the routine, we'll decide what we want
			// to do with the numbers it generated based on why the routine
			// got called in the first place.
			adjustMinMax(computedMinimum,computedMaximum);
		
			// OK, we've now calculated the ideal min/max values
			// to show on the chart.
			// However, there may be reasons we want to make the actual range
			// of the chart spread a little bit further.
			// Specifically, if there are series that need a little extra space
			// around their data points, either for labels, or just pretty
			// drawing,or other reasons (like BubbleSeries which render more
			// than one axis in each dimension).
			// But whatever the final range ends up being, we'll use
			// the min/max values we have now as the outer labels of the chart.
			labelMinimum = computedMinimum;
			labelMaximum = computedMaximum;
			
			// When do we adjust for margins?
			// In general, it's if we autogenerated our min/max values
			// and the user has asked us to make nice clean numbers around them.
			// We also have two optimizations in here.
			// First of all, if we auto generated our min/max from data
			// and found that none of our data needed any space around
			// their values, then this would be wasted effort.
			// Secondly, there are times this function gets called
			// when we're looking for an estimate...so we don't want to bother
			// with margins at that point.
			if ((autoAdjust || autoGen) &&
				checkForMargins &&
				_cachedValuesHaveBounds)	
			{		
				adjustForMargins(minimumAxisLength);
			}
			
			// If the author asked for additional padding outside
			// the min/max values, give it to them here.
			// However, it's possible that our margin calculations
			// have already generated more padding than was requested.
			// In which case we don't add more.
			var us:Number = unitSize;
			if (!isNaN(_padding))
			{
				if (labelMinimum - computedMinimum < _padding * us)
					computedMinimum = labelMinimum - _padding * us;
				
				if (computedMaximum - labelMaximum < _padding * us)
					computedMaximum = labelMaximum + _padding * us;
			}

			// Authors don't get all the fun...
			// series can ask for padding too. 
			// Check and see if any series have asked for padding,
			// and do the same calculation for them.
			var cachedDataDescriptions:Array = dataDescriptions;
			for (var i:int = 0; i < cachedDataDescriptions.length; i++) 
			{
				var desc:DataDescription = 	cachedDataDescriptions[i]
				if (!isNaN(desc.padding))
				{
					if (isNaN(assignedMinimum) &&
						desc.min - computedMinimum < desc.padding * us)
					{
						computedMinimum = desc.min - desc.padding * us;
					}
					
					if (isNaN(assignedMaximum) &&
						computedMaximum - desc.max < desc.padding * us)
					{
						computedMaximum = desc.max + desc.padding * us;
					}
				}
			}

			if (computedMinimum == computedMaximum)
			{
				var us2:Number = unitSize/2;
				computedMinimum -= us2;
				computedMaximum += us2;
			}

			// Finally, if the min/max/interval has changed, invalidate it here.
			if (oldMin != computedMinimum ||
				oldMax != computedMaximum ||
				oldInterval != computedInterval)
			{
				labelCache = null;
				minorTickCache = null;
			}

			_regenerateAutoValues = false;
		}
	}

	/**
	 *  Invalidates the cached labels and tick marks that represent this axis's values. 
	 *  Derived classes should call this function whenever values used in the calculation 
	 *  of labels and tick marks change.
	 */
	protected function invalidateCache():void
	{
		minorTickCache = null;
		_regenerateAutoValues = true;			
		labelCache = null;
	}
	
	/**
	 *  @inheritDoc
	 */
	public function update():void
	{
		updateCache(true, 0);
	}

	/**
	 *  @private
	 */
	private function adjustForMargins(minimumAxisLength:Number):void
	{
		// Make sure we have room for bounded values.
		// These are values that specify required padding in screen space
		// (i.e., a bar chart will specify space for its label,
		// in screen space).
		// This is imperfect, because by necessity at this point we don't know
		// how much space we'll really have on the axis.
		// But if we calc now, and the axis gets smaller (it won't get bigger)
		// we'll be conservative but safe.
		
		var min:Number = computedMinimum;
		var max:Number = computedMaximum;

		var n:int;
		var i:int;

		var boundedValues:Array = [];
		var cachedDataDescriptions:Array = dataDescriptions;
		for (i = 0; i < cachedDataDescriptions.length; i++)
		{
			if (cachedDataDescriptions[i].boundedValues != null)
			{
				boundedValues = boundedValues.concat(
					cachedDataDescriptions[i].boundedValues);
			}
		}
				
		if (boundedValues.length > 0 && minimumAxisLength > 0)
		{
			if (isNaN(min))
				min = boundedValues[0].value;
			if (isNaN(max))
				max = boundedValues[0].value;

			var axisLength:Number = minimumAxisLength;
			var currentRange:Number = max - min;
			var itrCount:int = 0;
			n = boundedValues.length;
			
			var verify:Boolean = true;
			while (verify)
			{
				var lowestOverlap:Number = minimumAxisLength;
				var lowestValue:BoundedValue;
				var highestOverlap:Number = 0;
				var highestValue:BoundedValue;

				var v1:Number = max;
				var m1:Number = 0;
				var v2:Number = min;
				var m2:Number = 0;

				for (i = 0; i < n; i++)
				{
					var v:BoundedValue = boundedValues[i];
					
					var vpos:Number =
						(v.value - min) / currentRange * minimumAxisLength;
					
					if ((!isNaN(v.lowerMargin)) &&
						vpos - v.lowerMargin < lowestOverlap)
					{
						lowestOverlap = vpos - v.lowerMargin;
						lowestValue = v;
					}
					
					if ((!isNaN(v.upperMargin)) &&
						vpos + v.upperMargin> highestOverlap)
					{
						highestOverlap = vpos + v.upperMargin;
						highestValue = v;
					}
				}

				if (lowestOverlap > -0.0001 &&
					highestOverlap < minimumAxisLength + 0.0001)
				{
					break;
				}

				if (highestOverlap > minimumAxisLength)
				{
					v1 = highestValue.value;
					m1 = highestValue.upperMargin;
				}
				else
				{
					// If we're only adjusting one side of the equation,
					// we know we're 100% accurate.
					verify = false;
				}

				if (lowestOverlap < 0)
				{
					v2 = lowestValue.value;
					m2 = lowestValue.lowerMargin;
				}
				else
				{
					// If we're only adjusting one side of the equation,
					// we know we're 100% accurate.
					verify = false;
				}

				var p1:Number = minimumAxisLength - m1;
				min = (p1 * v2 - m2 * v1) / (p1 - m2);
				max = minimumAxisLength * (v1 - min) /
					  (minimumAxisLength - m1) + min;
				currentRange = max - min;

				if (itrCount++ == 3)
					break;
			}
		}

		var adjusted:Array = guardMinMax(min, max);
		if (adjusted != null)
		{
			min = adjusted[0];
			max = adjusted[1];	
		}

		if (isNaN(assignedMinimum))
			computedMinimum = min;
		if (isNaN(assignedMaximum))
			computedMaximum = max;
	}

	/**
	 *  An Array of DataDescription structures describing the data being represented by the chart.
	 *  An axis can use this property to generate values for properties, such as its range.
	 */
	protected function get dataDescriptions():Array
	{
		if (_cachedDataDescriptions == null)
		{
			_cachedDataDescriptions = describeData(requiredDescribedFields);
			_cachedValuesHaveBounds = false;			
		}
		return _cachedDataDescriptions;
	}

	/**
	 *  @private
	 */
	private function autoGenerate(checkForMargins:Boolean,
									minimumAxisLength:Number):void
	{
		
		var min:Number;
		var max:Number;
		var v:Object;

		var cachedDataDescriptions:Array = this.dataDescriptions;
		
		if (autoAdjust && baseAtZero)
			min = zeroValue;
		
		var n:int = cachedDataDescriptions.length;
		var val:Number;
			
		if (n > 0)
		{
			max = cachedDataDescriptions[0].max;
			
			min = isNaN(min) ?
				  cachedDataDescriptions[0].min :
				  Math.min(min,cachedDataDescriptions[0].min);
			
			for (var i:int = 0; i < n; i++)
			{
				var desc:DataDescription = cachedDataDescriptions[i];
				
				if (!isNaN(desc.min))
					min = Math.min(min,desc.min);
				if (!isNaN(desc.max))
					max = Math.max(max,desc.max);

				_cachedValuesHaveBounds = _cachedValuesHaveBounds ||
					(desc.boundedValues != null &&
					 desc.boundedValues.length > 0);
			}

		}

		var adjusted:Array = guardMinMax(min,max);

		if (adjusted != null)
		{
			min = adjusted[0];
			max = adjusted[1];	
		}

		if (isNaN(assignedMinimum))
			computedMinimum = min;
		if (isNaN(assignedMaximum))
			computedMaximum = max;
	}

	/**
	 *  Adjusts the generated or assigned range of the axis's labels. 
	 *  This method is called during the update cycle of the axis. Subclasses can override this method 
	 *  to do special processing on the values. By default, no adjustments are made to the range.
	 *  
	 *	@param minValue The computed minimum value.
	 *	@param maxValue The computed maximum value.
	 */
	protected function adjustMinMax(minValue:Number,
									maxValue:Number):void
	{
	}


	/**
	 *  Protects the range against invalid values for this axis type. 
	 *  This function is called during the update cycle of the axis to guarantee that invalid 
	 *  ranges are not generated. Subclasses can override this class and define logic that 
	 *  is appropriate to their axis type.
	 *  
	 *  @param min The computed minimum value.
	 *  @param max The computed maximum value.
	 *  
	 *  @return null if no adjustment is necessary, or an Array containing the adjusted 
	 *  values of the form <code>[min,max]</code>.
	 */
	protected function guardMinMax(min:Number, max:Number):Array
	{
		if (isNaN(min) || !isFinite(min))
			return [ 0, 100 ];

		else if (isNaN(max) || !isFinite(max))
			return [ min, min + 100 ];

		else if (min == max)
			return [ 0, 100 ];

		return null;
	}
}

}
